package com.cold.demo.web.util;

import javax.servlet.http.HttpServletRequest;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class HostUtil {

    private static LocalHostInfo localHostCache;
    private static LocalHostInfo firstNonLoopbackHostInfoCache;

    /**
     * 返回发出请求的客户机的主机名
     */
    public static String findRequestHost(HttpServletRequest request) {
        return request.getRemoteHost();
    }

    /**
     * 返回发出请求的客户机的端口号
     */
    public static int findRequestPort(HttpServletRequest request) {
        return request.getRemotePort();
    }

    /**
     * 通过 HttpServletRequest 获取这个请求的ip地址
     * 获取用户真实IP地址，不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址
     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值，究竟哪个才是真正的用户端的真实IP呢？
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     * 如：X-Forwarded-For：192.168.1.110, 192.168.1.120, 192.168.1.130,192.168.1.100
     * 用户真实IP为： 192.168.1.110
     */
    public static String findRequestIp(HttpServletRequest request) {
        return doFindRequestIp(request);
    }


    /**
     * 这种方式获取的 InetAddress
     * 对象的主机名是没有问题
     * 但是这里获取的ip地址有可能是不对的
     * 比如这里的方法可能会获取到虚拟机的ip地址
     *
     * @return
     * @throws UnknownHostException
     */
    public static LocalHostInfo findLocalHost() throws UnknownHostException {
        if (localHostCache != null) {
            return localHostCache;
        }
        InetAddress localHost = getLocalHost();
        LocalHostInfo localHostInfo = convertAddress(localHost);
        localHostCache = localHostInfo;
        return localHostInfo;
    }

    private static InetAddress getLocalHost() throws UnknownHostException {
        return InetAddress.getLocalHost();
    }

    public static StringBuilder findLocalMac() throws UnknownHostException, SocketException {
        return findLocalMac(getLocalHost());
    }

    public static StringBuilder findLocalMac(InetAddress address) throws SocketException {
        // 获得网络接口对象（即网卡），并得到mac地址，mac地址存在于一个byte数组中。
        byte[] mac = NetworkInterface.getByInetAddress(address).getHardwareAddress();
        StringBuilder sb = new StringBuilder();
        final char split = '-';
        for (int i = 0; i < mac.length; i++) {
            if (i != 0) {
                sb.append(split);
            }
            // mac[i] & 0xFF 是为了把byte转化为正整数
            String s = Integer.toHexString(mac[i] & 0xFF);
            sb.append(s.length() == 1 ? 0 + s : s);
        }
        return sb;
    }

    /**
     * 获取一个 已启动的、非虚拟的、非回环地址的一个 HostInfo，如果未找到合适的地址，就返回一个 HostInfo 的默认对象
     *
     * @return
     * @throws SocketException
     */
    public static LocalHostInfo findFirstNonLoopbackHostInfo() throws SocketException {
        if (firstNonLoopbackHostInfoCache != null) {
            return firstNonLoopbackHostInfoCache;
        }
        List<InetAddress> localIPs = getLocalIPs();
        if (localIPs.size() > 0) {
            return (firstNonLoopbackHostInfoCache = convertAddress(localIPs.get(0)));
        }
        return new LocalHostInfo();
    }


    /**
     * 判断操作系统是否是Windows
     *
     * @return
     */
    public static boolean isWindowsOS() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }


    private static LocalHostInfo convertAddress(InetAddress address) {
        // 这里会花很多时间
        String hostname = address.getHostName();
        return new LocalHostInfo(hostname,address.getHostAddress());
    }


    /**
     * 获取所有网卡IP，排除回文地址、虚拟地址
     *
     * @return
     * @throws SocketException
     */
    private static List<InetAddress> getLocalIPs() throws SocketException {
        List<InetAddress> list = new ArrayList<>();
        Enumeration<NetworkInterface> faces = NetworkInterface.getNetworkInterfaces();
        // 遍历网络接口
        while (faces.hasMoreElements()) {
            NetworkInterface face = faces.nextElement();
            Enumeration<InetAddress> addresses = face.getInetAddresses();
            //   未启动        | 虚拟网卡           | 回环网卡            | address无任何东西  的都过滤掉
            if (!face.isUp() || face.isVirtual() || face.isLoopback() || !addresses.hasMoreElements()) {
                continue;
            }
            // 遍历网络地址
            while (addresses.hasMoreElements()) {
                InetAddress address = addresses.nextElement();
                // ipv4 、 非回环地址
                if (address instanceof Inet4Address
                        && !address.isLoopbackAddress()
                        && address.isSiteLocalAddress()
                        && !address.isAnyLocalAddress()) {
                    list.add(address);
                }
            }
        }
        return list;
    }

    private static String doFindRequestIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        String unknown = "unknown";
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


    public static class LocalHostInfo {

        private String ipAddress = "127.0.0.1";

        private String hostname = "localhost";

        public LocalHostInfo() { }

        public LocalHostInfo(String ipAddress, String hostname) {
            this.ipAddress = ipAddress;
            this.hostname = hostname;
        }

        public int getIpAddressAsInt() {
            InetAddress inetAddress = null;
            String host = this.ipAddress;
            if (host == null) {
                host = this.hostname;
            }
            try {
                inetAddress = InetAddress.getByName(host);
            } catch (final UnknownHostException e) {
                throw new IllegalArgumentException(e);
            }
            return ByteBuffer.wrap(inetAddress.getAddress()).getInt();
        }

        public String getIpAddress() {
            return this.ipAddress;
        }

        private void setIpAddress(String ipAddress) {
            this.ipAddress = ipAddress;
        }

        public String getHostname() {
            return this.hostname;
        }

        private void setHostname(String hostname) {
            this.hostname = hostname;
        }

        @Override
        public String toString() {
            return "HostInfo{" +
                    "ipAddress='" + ipAddress + '\'' +
                    ", hostname='" + hostname + '\'' +
                    '}';
        }
    }


    public static void clear(){
        localHostCache = null;
        firstNonLoopbackHostInfoCache = null;
    }

}
